#pragma once
#include "PageCache.hpp"

PageCache PageCache::_instance;

Span *PageCache::GetPageSpan(size_t k)
{
    // 我们需要遍历k页page下面的spanlist，返回一个不为空的span
    // 首先我们要判断当前页是否为空
    // 如果当前页的span不为空，我们直接头删，然后返回对应的span
    if (!_spanlist[k].Empty())
    {
        // 头删
        Span *span = _spanlist->PopFront();
        // 每次进行插入删除操作后，都要重新建立pageid和span的映射关系
        // 头删之后，当前桶后面的每一个span节点都要更新它的pageid和span的映射关系
        for (PAGE_ID i = 0; i < span->_pagenum; i++)
        {
            _ID_Span_map[span->_pageid + i] = span;
        }
        return span;
    }
    // 走到这里，说明k页的page里面没有非空的span，那么就需要向后找
    // 如果有就把他切分，如果没有就在堆上开辟空间
    for (size_t i = k + 1; i < MAX_PAGENUM; i++)
    {
        if (!_spanlist[i].Empty())
        {
            // 将大页进行切分，分成k页的span和i-k页的span
            // 然后将切分好的span挂到相应的page下面
            // 将后面大块的span分成k页的span和i-k页的span
            // 那么我们如和切分呢？
            // 我们给span增加几个参数，页号pageid，标识当前页的页号，每个桶下面挂了许多不同的span，每一个span都有自己的id
            // 页的位置spanNum，标识当前页在哪一个桶下面
            Span *nspan = _spanlist[i].PopFront();
            Span *kspan = new Span;
            kspan->_pageid = nspan->_pageid;
            kspan->_pagenum = k;

            nspan->_pageid += k;
            nspan->_pagenum -= k;
            // 建立nspan首位页号和最末尾页号与nspan的映射，方便pagecache回收内存时的合并查找
            _ID_Span_map[nspan->_pageid] = nspan;
            _ID_Span_map[nspan->_pageid + nspan->_pagenum - 1] = nspan;
            _spanlist[nspan->_pagenum].PushFront(nspan);
            // 更新kspan后面每一个span节点的pageid与span的映射关系
            for (PAGE_ID j = 0; j < kspan->_pagenum; j++)
            {
                _ID_Span_map[kspan->_pageid + j] = kspan;
            }
            return kspan;
        }
    }
    // 走到这里，说明k位置后面已经没有大页的span了，我们需要向操作系统申请空间
    // 申请的空间大小是128kb，即128页的大页空间
    Span *bigSpan = new Span;
    void *ptr = SystemAlloc(MAX_PAGENUM - 1);
    // 将指针转换为页 id（先转为 size_t 再右移）
    bigSpan->_pageid = (PAGE_ID)((size_t)ptr >> BIT_SHIFT);
    bigSpan->_pagenum = MAX_PAGENUM - 1;
    bigSpan->_freelist = nullptr;
    bigSpan->_isUse = false;
    _spanlist[bigSpan->_pagenum].PushFront(bigSpan);
    // 当我们申请好了大块内存之后，我们需要对大块内存进行切分，然后才能交给上层使用
    // 为了不写重复代码，我们采用递归的方式
    return GetPageSpan(k);
}

// 获取对象与span的映射关系，从而将span返回
Span *PageCache::GetObjectToSpan(void *obj)
{
    // 这里传递的参数是对象的地址，我们只需要让这个地址进行移位操作，即除以8kb的大小，就可以知道当前对象的pageid
    PAGE_ID id = (PAGE_ID)((size_t)obj >> BIT_SHIFT);
    auto ret = _ID_Span_map.find(id);
    if (ret != _ID_Span_map.end())
    {
        return ret->second;
    }
    else
    {
        assert(false);
        return nullptr;
    }
}

// 回收central cache交给page cache的空间
void PageCache::RecycleCentralCacheMemory(Span *span)
{
    //对于span前后的页，我们尝试进行合并，解决内存外碎片的问题
    while(1)
    {
        PAGE_ID prevID=span->_pageid-1;
        auto ret=_ID_Span_map.find(prevID);
        //如果找不到对应的span就返回
        if(ret==_ID_Span_map.end())
        {
            break;
        }
        //注意，我们此时是按照pageid的形式找到前面的span，如果当前span正在被上层使用呢？
        //此时我们不能回收正在被使用的span，那么我们如何知道当前span是否被使用呢？
        //我们需要在span里面增加一个标志位isuse，来判断span是否被使用
        Span*prevSpan=ret->second;
        if(prevSpan->_isUse==true)
        {
            break;
        }
        if(prevSpan->_pagenum+span->_pagenum>MAX_PAGENUM-1)
        {
            break;
        }
        span->_pageid=prevSpan->_pageid;
        span->_pagenum+=prevSpan->_pagenum;
        _spanlist[prevSpan->_pagenum].Erase(prevSpan);
        delete prevSpan;
    }

    //向后合并
    while(1)
    {
        PAGE_ID nextID=span->_pageid+span->_pagenum;
        auto ret=_ID_Span_map.find(nextID);
        if(ret==_ID_Span_map.end())
        {
            break;
        }
        Span*nextSpan=ret->second;
        if(nextSpan->_isUse==true)
        {
            break;
        }
        if(nextSpan->_pagenum+span->_pagenum>MAX_PAGENUM-1)
        {
            break;
        }

        span->_pagenum += nextSpan->_pagenum;
        // 应当从 nextSpan 所属的桶中擦除 nextSpan 本身
        _spanlist[nextSpan->_pagenum].Erase(nextSpan);
        delete nextSpan;
    }

    _spanlist[span->_pagenum].PushFront(span);
    span->_isUse=false;
    _ID_Span_map[span->_pageid]=span;
    _ID_Span_map[span->_pagenum-1+span->_pageid]=span;
}